#include <stx/test/test.hpp>

TEST_CASE (core_misc)
{
    char const* argv[] = {
        "hello.exe",
        "-a",
        "-b",
        "short_arg",
        "-c",
        "-d=short_opt_arg",
        "--long_option_with_required_arg",
        "long_arg",
        "--long_option_with_optional_arg1=long_opt_arg1",
        "--long_option_with_optional_arg2",
        "--long_option_with_no_arg",
        "arg1",
        "arg2",
    };

    auto const pcl =
        ProgramArgParser{}
            .addShortOptions("ab:c;d;"sv)
            .addLongOptionWithRequiredArg("long_option_with_required_arg"sv)
            .addLongOptionWithOptionalArg("long_option_with_optional_arg1"sv)
            .addLongOptionWithOptionalArg("long_option_with_optional_arg2"sv)
            .addLongOptionWithOptionalArg("long_option_with_optional_arg3"sv)
            .addLongOptionWithNoArg("long_option_with_no_arg"sv)
            .parse(std::size(argv), std::bit_cast<char**>(&argv));

    for (auto const& e : pcl.opts_with_no_arg)
    {
        CHECK("a"sv == e || "long_option_with_no_arg"sv == e);
    }

    for (auto const& [name, arg] : pcl.opts_with_arg)
    {
        auto const b1 = "b"sv == name && "short_arg"sv == arg;
        auto const b2 =
            "long_option_with_required_arg"sv == name && "long_arg"sv == arg;
        CHECK(b1 || b2);
    }

    for (auto const& [name, arg] : pcl.opts_with_opt_arg)
    {
        auto const b1 = "c"sv == name && ""sv == arg;
        auto const b2 = "d"sv == name && "short_opt_arg"sv == arg;
        auto const b3 = "long_option_with_optional_arg1"sv == name &&
                        "long_opt_arg1"sv == arg;
        auto const b4 =
            "long_option_with_optional_arg2"sv == name && ""sv == arg;
        CHECK(b1 || b2 || b3 || b4);
    }

    for (auto const& e : pcl.program_args)
    {
        CHECK("arg1"sv == e || "arg2"sv == e);
    }

    char const* argv2[] = {
        "hello.exe",
        "-a",
        "-b",
        "short_arg",
        "-c",
        "-d",
        "short_opt_arg",
        "--long_option_with_required_arg",
        "long_arg",
        "--long_option_with_optional_arg1=long_opt_arg1",
        "--long_option_with_optional_arg2",
        "long_opt_arg2",
        "--long_option_with_optional_arg3",
        "--long_option_with_no_arg",
        "-c",
        "arg1",
        "arg2",
    };

    auto is_caught = false;

    try
    {
        auto const pcl2 =
            ProgramArgParser{}
                .addShortOptions("ab:c;d;"sv)
                .addLongOptionWithRequiredArg("long_option_arg"sv)
                .addLongOptionWithOptionalArg("long_option_arg"sv)
                .addLongOptionWithNoArg("long_option_arg"sv)
                .parse(std::size(argv2), std::bit_cast<char**>(&argv2));
    }
    catch (std::exception const& e)
    {
        CHECK(startsWith(e.what(), "Duplicate option names"sv));
        is_caught = true;
    }

    CHECK(is_caught);

    is_caught = false;

    try
    {
        auto const pcl2 =
            ProgramArgParser{}
                .addShortOptions("ab:c;d;"sv)
                .addLongOptionWithRequiredArg("a"sv)
                .addLongOptionWithOptionalArg("long_option_arg"sv)
                .addLongOptionWithNoArg("long_option_arg"sv)
                .parse(std::size(argv2), std::bit_cast<char**>(&argv2));
    }
    catch (std::exception const& e)
    {
        CHECK(startsWith(e.what(), "Duplicate option names"sv));
        is_caught = true;
    }

    CHECK(is_caught);

    is_caught = false;

    try
    {
        auto const pcl2 =
            ProgramArgParser{}
                .addShortOptions("ab:c;d;"sv)
                .addLongOptionWithRequiredArg("long_option_arg"sv)
                .addLongOptionWithOptionalArg("long_option_arg"sv)
                .addLongOptionWithNoArg("long_option_arg2"sv)
                .parse(std::size(argv2), std::bit_cast<char**>(&argv2));
    }
    catch (std::exception const& e)
    {
        CHECK(startsWith(e.what(), "Duplicate option names"sv));
        is_caught = true;
    }

    CHECK(is_caught);

    is_caught = false;

    try
    {
        auto const pcl2 =
            ProgramArgParser{}
                .addShortOptions("ab:c;d;"sv)
                .addLongOptionWithRequiredArg("long_option_arg"sv)
                .addLongOptionWithOptionalArg("long_option_arg2"sv)
                .addLongOptionWithNoArg("long_option_arg"sv)
                .parse(std::size(argv2), std::bit_cast<char**>(&argv2));
    }
    catch (std::exception const& e)
    {
        CHECK(startsWith(e.what(), "Duplicate option names"sv));
        is_caught = true;
    }

    CHECK(is_caught);

    is_caught = false;

    try
    {
        auto const pcl2 =
            ProgramArgParser{}
                .addShortOptions("ab:c;d;"sv)
                .addLongOptionWithRequiredArg("long_option_arg2"sv)
                .addLongOptionWithOptionalArg("long_option_arg"sv)
                .addLongOptionWithNoArg("long_option_arg"sv)
                .parse(std::size(argv2), std::bit_cast<char**>(&argv2));
    }
    catch (std::exception const& e)
    {
        CHECK(startsWith(e.what(), "Duplicate option names"sv));
        is_caught = true;
    }

    CHECK(is_caught);

    struct A1_
    {
        int n = 123;
    };

    auto s_r = std::string{};
    visitAllFields([&](auto const n) { s_r = tos(n); }, A1_{});
    CHECK("123"sv == s_r);

    struct A2_
    {
        int         n1 = 123;
        std::size_t n2 = 789;
    };

    s_r.clear();
    visitAllFields([&](auto const n) { s_r += tos(n); }, A2_{});
    CHECK("123789"sv == s_r);

    struct A3_
    {
        int         n1 = 123;
        std::string s1 = "hello"s;
        std::size_t n2 = 789;
    };

    s_r.clear();
    visitAllFields([&](auto const n) { s_r += tos(n); }, A3_{});
    CHECK("123hello789"sv == s_r);
}